package net.xo68.destiny.starter.worker.handler.impl;

import com.sun.jna.Platform;
import lombok.extern.slf4j.Slf4j;
import net.xo68.destiny.contract.common.SystemConstants;
import net.xo68.destiny.contract.domain.job.TriggerParameter;
import net.xo68.destiny.contract.domain.report.DatagramSerializer;
import net.xo68.destiny.contract.domain.report.JobHandleReplyDatagram;
import net.xo68.destiny.contract.enums.handler.JobHandlePhaseEnum;
import net.xo68.destiny.contract.enums.handler.JobHandleStateEnum;
import net.xo68.destiny.core.thread.NamedThreadFactory;
import net.xo68.destiny.core.timingwheel.process.EventProcessorContext;
import net.xo68.destiny.core.util.ProcessUtil;
import net.xo68.destiny.starter.worker.SchedulerWorkerProperties;
import net.xo68.destiny.starter.worker.enums.JobLocalConfigType;
import net.xo68.destiny.starter.worker.handler.JobHandleReplyProvider;
import net.xo68.destiny.starter.worker.handler.JobLocalConfigStore;
import net.xo68.destiny.starter.worker.handler.JobLocalConfigStoreFactory;
import net.xo68.destiny.starter.worker.handler.JobSourceCodeType;
import net.xo68.destiny.starter.worker.logger.JobLogger;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 源代码任务抽象处理器
 * @author wuxie
 * @version 2020-11-23
 */
@Slf4j
public abstract class AbstractSourceCodeJobHandler extends AbstractJobHandler {

    private final JobLocalConfigStoreFactory jobLocalConfigStoreFactory;

    public AbstractSourceCodeJobHandler(SchedulerWorkerProperties schedulerWorkerProperties, JobHandleReplyProvider jobHandleReplyProvider, DatagramSerializer datagramSerializer, JobLocalConfigStoreFactory jobLocalConfigStoreFactory) {
        super(schedulerWorkerProperties,jobHandleReplyProvider,datagramSerializer);
        this.jobLocalConfigStoreFactory = jobLocalConfigStoreFactory;
    }

    /**
     * 控制台编码
     * @return
     */
    public String consoleEncoding(){
        if(StringUtils.isNoneBlank(schedulerWorkerProperties.getConsoleEncoding())){
            return schedulerWorkerProperties.getConsoleEncoding();
        }
        String encoding= SystemConstants.ENCODE_DEFAULT;
        if(Platform.isWindows()){
            encoding=SystemConstants.ENCODE_GBK;
        }
        return encoding;
    }

    /**
     * 任务源代码类型
     * @return
     */
    public abstract JobSourceCodeType jobSourceCodeType();

    @Override
    public void execute(EventProcessorContext<TriggerParameter> processorContext) throws Exception {
        JobSourceCodeType sourceCodeType=jobSourceCodeType();
        if(!sourceCodeType.isScript()){
            log.error("不支持非脚本源码执行");
        }
        JobLocalConfigStore localConfigStore=jobLocalConfigStoreFactory.create(JobLocalConfigType.SOURCE,jobLogger,processorContext.triggerParameter(),sourceCodeType.getSuffix());

        TriggerParameter triggerParameter=processorContext.triggerParameter();
        if(StringUtils.isBlank(triggerParameter.getJobDetailExtends().getSourceCode())){
            jobLogger.log("任务: {}，的脚本内容是空的", triggerParameter.getJobId());
            JobHandleReplyDatagram replyDatagram=JobHandleReplyDatagram.builder(this.datagramSerializer).instanceId(triggerParameter.getInstanceId())
                    .phase(JobHandlePhaseEnum.FINISH)
                    .state(JobHandleStateEnum.FAIL)
                    .datagram("脚本内容是空的，请配置!~")
                    .build();
            setHandleResult(processorContext,replyDatagram);
            return;
        }
        if(Platform.isWindows() && sourceCodeType.equals(JobSourceCodeType.WINDOWS_CMD) && StringUtils.isNotBlank(triggerParameter.getJobDetailExtends().getSourceCode())){
            //triggerParameter.setJobSource("chcp 65001\n" + triggerParameter.getJobSource());
            String newSourceCode="chcp 65001\n" +triggerParameter.getJobDetailExtends().getSourceCode();
            triggerParameter.getJobDetailExtends().setSourceCode(newSourceCode);
        }

        // make script file
        String scriptFileName =localConfigStore.getSourcePath();
        localConfigStore.save();

        jobLogger.log("----------- script file:{}",localConfigStore.getSourcePath());
        int exitValue = execScript(scriptFileName,processorContext.triggerParameter());
        if(exitValue==0){
            JobHandleReplyDatagram replyDatagram=JobHandleReplyDatagram.builder(this.datagramSerializer).instanceId(triggerParameter.getInstanceId())
                    .phase(JobHandlePhaseEnum.FINISH)
                    .state(JobHandleStateEnum.SUCCESS)
                    .datagram("源码脚本执行成功")
                    .build();
            setHandleResult(processorContext,replyDatagram);
        }else {
            JobHandleReplyDatagram replyDatagram=JobHandleReplyDatagram.builder(this.datagramSerializer).instanceId(triggerParameter.getInstanceId())
                    .phase(JobHandlePhaseEnum.FINISH)
                    .state(JobHandleStateEnum.FAIL)
                    .datagram("源码脚本执行失败")
                    .build();
            setHandleResult(processorContext,replyDatagram);
        }
        //processorContext.setResult();
    }

    protected int execScript(String scriptFile, TriggerParameter triggerParameter){

        Thread inputThread = null;
        Thread errThread = null;
        try{
            String cmd = jobSourceCodeType().getCmd();
            // command
            final List<String> cmdarray = new ArrayList<>();
//            if(Platform.isWindows()){
//                cmdarray.add("chcp");
//                cmdarray.add("65001&");
//                //cmdarray.add("&&");
//            }
            if(jobSourceCodeType().isInputCmd()){
                final String[] cmds=cmd.split(" ");
                Arrays.stream(cmds).forEach(cmdStr->{
                    cmdarray.add(cmdStr);
                });
                //cmdarray.add(cmd);
            }
            cmdarray.add(scriptFile);
            String[] cmdarrayFinal = cmdarray.toArray(new String[cmdarray.size()]);

            log.debug("script command: {}", cmdarray);
            // process-exec
            final Process process = Runtime.getRuntime().exec(cmdarrayFinal);


            String prcsId = ProcessUtil.getProcessId(process);
            jobLogger.log("------------------process id: {}",prcsId);
            JobHandleReplyDatagram runningReplyDatagram=JobHandleReplyDatagram.builder(datagramSerializer).instanceId(triggerParameter.getInstanceId())
                    .phase(JobHandlePhaseEnum.RUNNING)
                    .build(prcsId);
            publishDatagram(runningReplyDatagram);

            NamedThreadFactory threadFactory=new NamedThreadFactory("executorJobHandler");
            // log-thread
            inputThread = threadFactory.newThread(() -> {
                try {
                    writeProcessLog(process.getInputStream(), jobLogger);
                } catch (Exception e) {
                    jobLogger.log(e.getMessage(),e);
                }
            });
            errThread = threadFactory.newThread(() -> {
                try {
                    writeProcessLog(process.getErrorStream(),jobLogger);
                } catch (Exception e) {
                    jobLogger.log(e.getMessage(),e);
                }
            });
            inputThread.start();
            errThread.start();

            // process-wait
            // exit code: 0=success, 1=error
            int exitValue=process.waitFor();
//            if(process.isAlive()){
//                exitValue = process.waitFor();
//            }else {
//                exitValue = process.exitValue();
//            }


            // log-thread join
            inputThread.join();
            errThread.join();

            return exitValue;

        } catch (Exception e) {
            jobLogger.log(e.getMessage(),e);
            return -1;
        } finally {
            if (inputThread != null && inputThread.isAlive()) {
                inputThread.interrupt();
            }
            if (errThread != null && errThread.isAlive()) {
                errThread.interrupt();
            }
        }
        //return -1;
    }

    /**
     * 监听控制台输出
     * @param inputStream
     * @param jobLogger
     * @return
     * @throws IOException
     */
    protected long writeProcessLog(InputStream inputStream, final JobLogger jobLogger) throws IOException {

        long total = 0;
        try{
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, consoleEncoding()));
            String line;
            while ((line = reader.readLine()) != null){
                jobLogger.log(line,false);
                total++;
            }
            reader.close();
            inputStream = null;
            return total;
        } catch (IOException e) {
            jobLogger.log(e.getMessage(),e);
        }finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
        return 0L;
    }
}
